P06-2. 실제 Linux VM 하이브리드 서비스 메시 - 실습 검증
Part 06 (실습): 실제 Linux VM 하이브리드 서비스 메시 검증
- 쿠버네티스 클러스터 바깥의 Docker
kind브릿지 네트워크(172.19.0.0/16)에 실제 PostgreSQL 18 Linux VM 컨테이너(study-pg-vm) 를 기동하고 사설 IP(172.19.0.x)를 할당받는다. WorkloadGroup·WorkloadEntry·ServiceEntry·Service4종 세트를 배포하여 외부 Linux VM을 Ambient Mesh의 정식 워크로드 및 서비스로 편입시킨다.- 쿠버네티스 내부의
pg-client파드에서ztunnel내장 DNS 프록시(127.0.0.1:15053)가 응답해 주는 가상 IP(240.240.0.50)와HBONE(:15008)터널을 타고pg-vm.mesh.internal:5432외부 DB VM에 접속해 실제 테이블 생성 및 데이터 저장(INSERT/SELECT) 을 수행한다. - 설령 해커가 DB 비밀번호(
meshpass)를 탈취했더라도, 허가되지 않은 파드(rogue-db-client)가 외부 DB VM에 접속하려 할 때Waypoint Proxy가 SPIFFE 신분증을 검사해 DB 앞단에서 연결을 즉시 차단하는 하이브리드 Zero-Trust 보안을 실측 증명한다.
- P06-1 이론 개념을 먼저 읽고 오면
WorkloadGroup(VM용 Deployment),WorkloadEntry(VM용 Pod),ServiceEntry(커스텀 전화번호부)의 1:1 매핑 구조가 명확히 이해됩니다. - Windows PowerShell 창을 열고 아래 명령어로 현재 클러스터 상태를 확인합니다.
$env:Path += ";$HOME\bin" cd "$HOME\mesh-study" kubectl get nodes -o wide
실습 아키텍처 및 하이브리드 연동 구조 한눈에 보기
flowchart LR
subgraph K8S_CLUSTER["Kubernetes 클러스터 내부 (part1-demo 네임스페이스)"]
PG_GOOD["pg-client 파드
(신분: sa/default)
PGPASSWORD=meshpass"]
PG_BAD["rogue-db-client 파드
(침입자: sa/rogue-sa)
PGPASSWORD=meshpass (비번 탈취!)"]
ZT["Node ztunnel
- 내장 DNS (:15053) -> 240.240.0.50 응답
- TCP :15001 납치 -> HBONE (:15008)"]
WP["Waypoint Proxy
- AuthorizationPolicy 검문!
- WorkloadEntry(172.19.0.7)로 라우팅"]
end
subgraph EXT_NET["클러스터 외부 · Docker kind 브릿지 네트워크 (172.19.0.0/16)"]
VM["study-pg-vm (외부 Linux VM)
PostgreSQL 18.6 (IP: 172.19.0.7:5432)
DB: ordersdb"]
end
PG_GOOD -->|"psql -h pg-vm.mesh.internal"| ZT
PG_BAD -->|"psql -h pg-vm.mesh.internal"| ZT
ZT ==>|"HBONE (:15008)"| WP
WP ==>|"1. sa/default 통과! -> SQL 실행"| VM
WP -.->|"2. sa/rogue-sa 즉시 연결 차단!
(DB 비번이 맞아도 접근 불가!)"| PG_BADStep 1. 클러스터 외부에 실제 PostgreSQL 18 Linux VM(study-pg-vm) 기동
Docker Desktop 내장 3노드 클러스터(desktop-control-plane, desktop-worker, desktop-worker2)는 Docker의 kind 브릿지 네트워크(172.19.0.0/16) 위에 연결되어 있습니다.
실무에서 쿠버네티스 노드들과 같은 VPC 서브넷(또는 Peering 네트워크)에 떠 있는 외부 Linux DB VM을 완벽히 재현하기 위해, 쿠버네티스 파드가 아닌 독립된 Docker 컨테이너(study-pg-vm) 를 --network kind에 직접 띄웁니다!
# 1. 기존 컨테이너가 있다면 정리 후, kind 브릿지 네트워크에 외부 PostgreSQL 18 VM 기동
docker rm -f study-pg-vm 2>$null
docker run -d --name study-pg-vm --network kind `
-e POSTGRES_USER=meshuser `
-e POSTGRES_PASSWORD=meshpass `
-e POSTGRES_DB=ordersdb `
postgres:18-alpine
# 2. DB 초기화를 위해 4초 대기 후, 할당된 외부 VM의 사설 IP(172.19.0.x) 추출!
Start-Sleep -Seconds 4
$vmIP = docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" study-pg-vm
Write-Output "External VM IP on kind network: $vmIP"
실제 출력 결과:
8539b71586a91ed9c3003d99c667dcdefac674ecd715261a1723de4b62bde9ff
External VM IP on kind network: 172.19.0.7
- 쿠버네티스 노드들(
172.19.0.2 ~ .4) 옆에172.19.0.7IP를 가진 독립된 PostgreSQL 18 Linux VM(study-pg-vm) 이 탄생했습니다! - 이 컨테이너는
kubectl get pods를 아무리 쳐도 나오지 않는 완벽한 클러스터 외부 서버입니다.
Step 2. 외부 Linux VM을 서비스 메시의 정식 가족으로 호적 등재 (WorkloadEntry & ServiceEntry)
이제 위에서 알아낸 $vmIP(172.19.0.7)를 이용해 WorkloadGroup, WorkloadEntry, ServiceEntry, 그리고 쿠버네티스 표준 Service(external-pg) 를 한 번에 등록합니다.
# part6-hybrid-vm.yaml 생성 및 적용 ($vmIP 변수가 자동으로 실제 IP로 치환되어 들어갑니다!)
@"
apiVersion: networking.istio.io/v1
kind: WorkloadGroup
metadata:
name: pg-vm-group
namespace: part1-demo
spec:
metadata:
labels:
app: external-pg-vm
class: vm
template:
serviceAccount: default
network: vm-network
---
apiVersion: networking.istio.io/v1
kind: WorkloadEntry
metadata:
name: study-pg-vm-01
namespace: part1-demo
spec:
address: $vmIP
labels:
app: external-pg-vm
class: vm
instance: study-pg-vm-01
serviceAccount: default
---
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-pg-se
namespace: part1-demo
spec:
hosts:
- pg-vm.mesh.internal
addresses:
- 240.240.0.50
ports:
- number: 5432
name: tcp-postgres
protocol: TCP
location: MESH_INTERNAL
resolution: STATIC
workloadSelector:
labels:
app: external-pg-vm
---
apiVersion: v1
kind: Service
metadata:
name: external-pg
namespace: part1-demo
spec:
selector:
app: external-pg-vm
ports:
- name: tcp-postgres
port: 5432
targetPort: 5432
"@ | Set-Content -Path "$HOME\mesh-study\part6-hybrid-vm.yaml" -Encoding utf8
kubectl apply -f "$HOME\mesh-study\part6-hybrid-vm.yaml"
실제 출력 결과:
workloadgroup.networking.istio.io/pg-vm-group created
workloadentry.networking.istio.io/study-pg-vm-01 created
serviceentry.networking.istio.io/external-pg-se created
service/external-pg created
ztunnel 장부에 외부 VM이 어떻게 등록되었는지 확인!
과연 노드의 ztunnel이 클러스터 바깥의 study-pg-vm-01(172.19.0.7)을 파드 목록(workloads)과 서비스 목록(services)에 정식으로 올려놓았는지 확인합니다.
# 1. ztunnel이 인식한 서비스 목록 (external-pg 와 external-pg-se 확인!)
istioctl ztunnel-config services --service-namespace part1-demo
# 2. ztunnel이 인식한 워크로드 목록 (study-pg-vm-01 확인!)
istioctl ztunnel-config workloads --workload-namespace part1-demo
실제 출력 결과:
NAMESPACE SERVICE NAME SERVICE VIP WAYPOINT ENDPOINTS
part1-demo external-pg 10.96.75.26 waypoint 1/1
part1-demo external-pg-se 240.240.0.50 waypoint 1/1
part1-demo order-api 10.96.72.69 waypoint 3/3
part1-demo order-api-v1 10.96.72.172 waypoint 2/2
part1-demo order-api-v2 10.96.242.219 waypoint 1/1
part1-demo waypoint 10.96.27.87 None 1/1
NAMESPACE POD NAME ADDRESS NODE WAYPOINT PROTOCOL
part1-demo order-api-86d8bb68dd-mkvrw 10.244.2.6 desktop-worker None HBONE
part1-demo order-api-86d8bb68dd-xh5rm 10.244.1.8 desktop-worker2 None HBONE
part1-demo order-api-v2-677cff8fd5-wz8sp 10.244.2.10 desktop-worker None HBONE
part1-demo study-client 10.244.2.7 desktop-worker None HBONE
part1-demo study-pg-vm-01 172.19.0.7 None TCP
part1-demo waypoint-6cc777d4c7-cwlx2 10.244.2.8 desktop-worker None TCP
ztunnel 장부에서 확인되는 3가지 핵심 포인트!
POD NAME: study-pg-vm-01 | ADDRESS: 172.19.0.7: 쿠버네티스 파드가 아닌 외부 Docker 컨테이너 VM인데도ztunnel의 워크로드 장부에 일반 파드들과 나란히 등재되었습니다! (노드 밖의 외부 VM이므로NODE칸은 비어 있고PROTOCOL: TCP로 표시됩니다.)external-pg-se | 240.240.0.50 | waypoint | 1/1:ServiceEntry로 만든 커스텀 도메인(pg-vm.mesh.internal)이Waypoint와 연결되었고 살아있는 엔드포인트1/1(study-pg-vm-01) 을 정상 인식했습니다!external-pg | 10.96.75.26 | waypoint | 1/1: 일반 쿠버네티스Service도 라벨 셀렉터(app: external-pg-vm)를 통해 외부 VM을 자기 엔드포인트(1/1)로 자동 연결했습니다!
Step 3. 쿠버네티스 파드에서 외부 PostgreSQL 18 VM으로 실제 SQL 쿼리 실행!
이제 쿠버네티스 내부(part1-demo)에 PostgreSQL 클라이언트 파드(pg-client)를 하나 띄우고, IP 주소(172.19.0.7)를 전혀 몰라도 메시 도메인(pg-vm.mesh.internal 및 external-pg.part1-demo.svc.cluster.local)만으로 외부 DB VM에 접속해 실제 SQL 쿼리가 실행되는지 확인합니다!
# 1. K8s 클러스터 내부에 PostgreSQL 클라이언트 파드(pg-client) 기동
kubectl run pg-client -n part1-demo --image=postgres:18-alpine --restart=Never --env="PGPASSWORD=meshpass" -- sleep 3650d
kubectl wait --for=condition=Ready pod/pg-client -n part1-demo --timeout=90s
# 2. ServiceEntry 커스텀 도메인(pg-vm.mesh.internal)으로 외부 VM에 접속해 DB 버전 및 실제 서버 IP 조회!
kubectl exec -n part1-demo pg-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT version(), current_database(), inet_server_addr(), inet_server_port();"
# 3. K8s 표준 서비스 도메인(external-pg.part1-demo.svc.cluster.local)으로 접속해 주문 테이블 생성 및 INSERT!
kubectl exec -n part1-demo pg-client -- psql -h external-pg.part1-demo.svc.cluster.local -U meshuser -d ordersdb -c "CREATE TABLE IF NOT EXISTS mesh_orders (id SERIAL PRIMARY KEY, item TEXT, created_at TIMESTAMPTZ DEFAULT now()); INSERT INTO mesh_orders (item) VALUES ('Hybrid Mesh VM Order #1') RETURNING *;"
실제 출력 결과:
pod/pg-client created
pod/pg-client condition met
version | current_database | inet_server_addr | inet_server_port
-----------------------------------------------------------------------------------------+------------------+------------------+------------------
PostgreSQL 18.6 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit | ordersdb | 172.19.0.7 | 5432
(1 row)
CREATE TABLE
id | item | created_at
----+-------------------------+-------------------------------
1 | Hybrid Mesh VM Order #1 | 2026-10-25 07:16:59.663021+00
(1 row)
INSERT 0 1
- 쿠버네티스 안의
pg-client파드가pg-vm.mesh.internal로 쿼리를 날리자,ztunnel(:15053DNS +:15008HBONE)과Waypoint를 거쳐 정확히 클러스터 바깥의PostgreSQL 18.6(inet_server_addr: 172.19.0.7:5432) 에 도착해Hybrid Mesh VM Order #1레코드를 저장하고 결과를 반환했습니다!
Step 4. 하이브리드 Zero-Trust 보안 — DB 비밀번호가 털려도 Waypoint가 외부 DB를 지킨다!
전통적인 환경에서는 해커가 클러스터 안의 파드 하나를 뚫고 환경변수에 적힌 DB 비밀번호(PGPASSWORD=meshpass) 를 훔쳐내면, 그 파드에서 외부 DB VM으로 접속해 모든 고객 데이터를 탈취할 수 있었습니다.
하지만 외부 DB VM을 우리 서비스 메시에 편입시킨 지금, AuthorizationPolicy로 "오직 허가된 서비스 계정(sa/default)만 외부 DB(external-pg / external-pg-se)의 5432 포트에 접속할 수 있다!" 고 지정하면 어떻게 될까요?
똑같은 진짜 DB 비밀번호(meshpass)를 가진 침입자 파드(rogue-db-client, 계정: rogue-sa) 를 띄워 직접 실험해 봅니다!
# 1. 진짜 DB 비밀번호(meshpass)를 알고 있는 침입자 파드(rogue-db-client)와 외부 DB 보호 정책(external-pg-guard) 배포!
@"
apiVersion: v1
kind: Pod
metadata:
name: rogue-db-client
namespace: part1-demo
spec:
serviceAccountName: rogue-sa
containers:
- name: psql
image: postgres:18-alpine
command: ["sleep", "3650d"]
env:
- name: PGPASSWORD
value: "meshpass"
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: external-pg-guard
namespace: part1-demo
spec:
targetRefs:
- kind: Service
group: ""
name: external-pg
- kind: ServiceEntry
group: networking.istio.io
name: external-pg-se
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/part1-demo/sa/default"]
to:
- operation:
ports: ["5432"]
"@ | kubectl apply -f -
kubectl wait --for=condition=Ready pod/rogue-db-client -n part1-demo --timeout=60s
# 2. 허가된 파드(pg-client, sa/default)에서 외부 DB 조회 -> 정상 성공!
kubectl exec -n part1-demo pg-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT * FROM mesh_orders;"
# 3. 비밀번호를 훔친 침입자 파드(rogue-db-client, sa/rogue-sa)에서 외부 DB 조회 -> Waypoint가 즉시 차단!
kubectl exec -n part1-demo rogue-db-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT * FROM mesh_orders;"
실제 출력 결과:
# [허가된 pg-client 결과: 정상 조회 성공!]
id | item | created_at
----+-------------------------+-------------------------------
1 | Hybrid Mesh VM Order #1 | 2026-10-25 07:16:59.663021+00
(1 row)
# [침입자 rogue-db-client 결과: DB 비밀번호가 맞아도 Waypoint가 연결 즉시 강제 종료!]
psql: error: connection to server at "pg-vm.mesh.internal" (240.240.0.50), port 5432 failed: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
command terminated with exit code 2
- 우리는 외부
study-pg-vm컨테이너의pg_hba.conf나 리눅스 방화벽을 전혀 건드리지 않았습니다. - 심지어 침입자 파드(
rogue-db-client)는 정확한 DB 아이디(meshuser)와 비밀번호(meshpass) 를 들고 접속했습니다. - 그럼에도 불구하고 패킷이 외부 VM으로 나가기 직전
Waypoint Proxy가 침입자 파드의SPIFFE전자 여권(sa/rogue-sa)을 적발하고 TCP 연결을 그 자리에서 끊어버렸습니다(server closed the connection unexpectedly)! 이것이 바로 3세대 하이브리드 서비스 메시가 실무 인프라(RDS/Cloud DB 연동)에서 발휘하는 진정한 위력입니다.
다음 파트(Part 07 PostgreSQL 심화)를 위한 정리
침입자 파드와 임시 차단 정책만 지우고, 외부 PostgreSQL 18 VM(study-pg-vm)과 하이브리드 메시 연결(pg-vm.mesh.internal)은 곧바로 이어질 Part 07(PostgreSQL 내부 아키텍처 — MVCC, Lock, pg_stat_statements)에서 그대로 사용하므로 유지합니다!
kubectl delete pod rogue-db-client -n part1-demo --wait=false
kubectl delete authorizationpolicy external-pg-guard -n part1-demo
Step 5. 자주 발생하는 문제와 해결 (Troubleshooting)
| 증상 / 에러 메시지 | 원인 | 해결 방법 |
|---|---|---|
psql -h pg-vm.mesh.internal 실행 시 could not translate host name "pg-vm.mesh.internal" to address 에러 |
1) ServiceEntry에 addresses: ["240.240.0.50"] 가상 IP를 지정하지 않았거나,2) 클라이언트 파드가 Ambient Mesh( istio.io/dataplane-mode=ambient)에 편입되지 않아 ztunnel DNS(127.0.0.1:15053)를 타지 않음 |
ServiceEntry에 고정 가상 IP(240.240.x.x)를 명시하고, 클라이언트 파드가 속한 네임스페이스에 istio.io/dataplane-mode=ambient 라벨이 있는지 확인 |
study-pg-vm 컨테이너를 다시 띄웠더니 연결이 타임아웃됨 |
컨테이너를 삭제 후 재생성할 때 Docker 브릿지 IP(172.19.0.x)가 다른 번호로 바뀌었을 수 있음 |
docker inspect로 새 IP를 확인한 뒤 WorkloadEntry(study-pg-vm-01)의 spec.address를 업데이트 |
ServiceEntry에 AuthorizationPolicy를 걸었는데 차단이 안 됨 |
targetRefs에 ServiceEntry를 지정할 때 group: networking.istio.io를 빠뜨렸거나 네임스페이스에 Waypoint가 없음 |
targetRefs에 group: networking.istio.io, kind: ServiceEntry를 정확히 명시하고 Waypoint가 동작 중인지 확인 |
Step 6. 이번 파트 핵심 명령어 정리 (Command Reference)
# 1. Docker kind 네트워크에 연결된 외부 VM 컨테이너의 사설 IP 확인
docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" study-pg-vm
# 2. 클러스터에 등록된 하이브리드 메시 리소스(WorkloadGroup, WorkloadEntry, ServiceEntry) 확인
kubectl get workloadgroup,workloadentry,serviceentry -n part1-demo
# 3. K8s 내부 파드에서 메시 도메인(pg-vm.mesh.internal)으로 외부 PostgreSQL 18 VM 쿼리 실행
kubectl exec -n part1-demo pg-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT * FROM mesh_orders;"
Step 7. 셀프 체크 (Self-Check Q&A)
Q1. 쿠버네티스의 Deployment, Pod, Service에 각각 1:1로 대응하는 Istio의 외부 VM 연동 리소스 3가지는 무엇인가요?
Deployment↔WorkloadGroup(외부 VM 그룹의 공통 템플릿·라벨·서비스 계정 정의)Pod↔WorkloadEntry(실제 외부 Linux VM 1대의 IP 주소와 라벨을 가진 명예 파드 등록증)Service↔ServiceEntry(커스텀 도메인pg-vm.mesh.internal과 가상 IP240.240.0.50을 부여하고workloadSelector로WorkloadEntry들을 묶어 주는 전화번호부)
Q2. 쿠버네티스 기본 CoreDNS에는 pg-vm.mesh.internal 도메인이 등록되어 있지 않은데, pg-client 파드는 어떻게 이 도메인의 IP(240.240.0.50)를 알아냈나요?
Ambient Mesh에 편입된 파드의 네트워크 네임스페이스 안에는 ztunnel의 내장 로컬 DNS 서버(127.0.0.1:15053) 가 열려 있어 파드의 DNS 요청을 먼저 가로챕니다. ztunnel은 istiod로부터 전달받은 ServiceEntry 장부를 보고 CoreDNS까지 가지 않고도 즉시 240.240.0.50 가상 IP를 응답해 줍니다.
🔗 다음 파트로 이동
축하합니다! Part 01부터 Part 06까지 달려오며 우리는 1세대 Sidecar 메시 → 2세대 Ambient Mesh (ztunnel + Waypoint) → Zero-Trust 보안 (SPIFFE/SVID) → L7 트래픽 제어 & 서킷 브레이커 → 3세대 하이브리드 Linux VM 연동 (PostgreSQL 18) 까지 서비스 메시의 전 세대를 100% 라이브 클러스터 위에서 정복했습니다!
다음 Part 07에서는 방금 하이브리드 메시에 연결한 PostgreSQL 18 (study-pg-vm)의 내부 아키텍처(Process-per-connection, MVCC, Row Lock 경합, pg_stat_statements)와 Terraform IaC 설계를 파헤치고, Part 08~12에서 대망의 풀스택 LGTM 관측 스택(Grafana Alloy + Loki + Grafana + Tempo + Mimir/VictoriaMetrics + Kiali) 을 이 위에 그대로 얹어냅니다!
- 다음 노트: P07-1. PostgreSQL 내부 구조와 IaC 설계 (이론 개념)
- 목록으로 이동: 0. 스터디 네비게이션